using System;
using System.Threading.Tasks;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;

namespace HPM.Console
{
	public static class Command
	{
		#region Help
		//TODO: replace help items with code comments generated xml and/or updatable documentation reference
		public static void showhelp ()
		{
			Program.newline ();
			Program.cout ("Type help (command name) for more info on each. Commands:");
			Program.newline ();
			Program.commands.ToList ().ForEach (x => Program.cout (x));
			Program.newline ();
		}
		
		public static void helpstart ()
		{
			Program.newline ();
			Program.cout ("Quick command to start currently chosen sim. Not case senstive.");
			Program.newline ();
		}
		
		public static void helpstop ()
		{
			Program.newline ();
			Program.cout ("Quick command to stop currently chosen sim. Not case senstive.");
			Program.newline ();
		}
		
		public static void helpcall ()
		{
			Program.newline ();
			Program.cout ("Invoke function for currently chosen sim. Specify function name as first argument; only parameterless void methods are currently implemented. Can be used to start and stop simulations. Not case senstive.");
			Program.newline ();
		}
		
		public static void helpshowfunctions ()
		{
			Program.newline ();
			Program.cout ("Show all functions of currently chosen sim that may be called. May be used without a space. May also use sf for shorthand.");
			Program.newline ();
		}
		
		public static void helpremovesimulation ()
		{
			Program.newline ();
			Program.cout ("Remove simulation from the list. Specify index as first argument. Stops sim if it is running. Note that all sims are reindexed after removal. Not case senstive. May be used without a space. May also use rs or removesim for shorthand.");
			Program.newline ();
		}
		
		public static void helpupdatevariable ()
		{
			Program.newline ();
			Program.cout ("Set variable for currently chosen sim. Specify variable name as first argument and value as second; currently values are only integers. For experimentation, variables may be changed from their defaults prior to starting and even during processing. Not case senstive. May be used without a space. May also use uv or updatevar for shorthand.");
			Program.newline ();
		}
		
		public static void helpshowvariables ()
		{
			Program.newline ();
			Program.cout ("Show all variables of currently chosen simulation. On initial sim creation, variables are always set to hardcoded defaults; these values may be changed later with this command. Variables may be set prior to starting and also while running. Not case senstive. May be used without a space. May also use sv or showvars for shorthand.");
			Program.newline ();
			Program.cout ("TimeDilation - Frame of reference to allow accelerated periods. This is a value in milliseconds equivalent to one day in relative simulation time. Default value is 1000; 1 day in simulation time is only perceived as 1 second to the observer.");
			Program.newline ();
			Program.cout ("MaxYears - Max number of years simulation will reach before stopping. The intential use of this variable is to create a ceiling on the sim runtime counter. This target year may not help indicate the max years in sim runtime if sim start date is updated initially or mid-process. Default sim start date is year 1; if this is unmodified; to determine actual runtime in simulation years evaluate max years minus sim start date.");
			Program.newline ();
			Program.cout ("TotalPersonPurposes - Quantity of purposes a new person is created with. Updates to this value while running only affect future generations of persons. Previous generations retain the same purposes as when they were created.");
			Program.newline ();
			Program.cout ("NumberOfPersons - Total number of persons in simulation. Updates to this value will increase or decrease the population accordingly, even while the simulation is active and running. This results in some additional computational resources to generate new persons with new purposes or the destruction of persons in the case of population decreases.");
			Program.newline ();
			Program.cout ("MaxPossiblePurposes - Used to conceptually represent a virtual array of indexes of size N, where N is the max number of possible purposes available to each person when choosing their purposes. Each purpose chosen per person is numerically understood as an index in the total of all available purposes. Changes to this value, even while the simulation is active and running, will increase or decrease the total purposes available to each person. This may result in new indexes not available to previous persons, or removal of indexes which previous persons have already chosen. In either case, previous persons are unaffected and still retain the value they chose previously.");
			Program.newline ();
		}
		
		public static void helpchoosesimulation ()
		{
			Program.newline ();
			Program.cout ("Choose a simulation to instantiate, set variables and start. Specify index of sim as argument. Index count starts at zero and is represented by order simulations are displayed by the command show simulations. Variables may be set prior to starting. Not case senstive. May be used without a space. May also use cs or choosesim for shorthand.");
			Program.newline ();
		}
		
		public static void helpnewsimulation ()
		{
			Program.newline ();
			Program.cout ("Create and instantiate a new simulation. Variables may be set prior to starting. Show variables or simulations to see default values. Not case senstive. May be used without a space. May also use ns or newsim for shorthand.");
			Program.newline ();
		}
		
		public static void helpshowsimulations ()
		{
			Program.newline ();
			Program.cout ("Show all created idle simulations and those actively running on seperate threads. Currently resets when application is exited. Not case senstive. May be used without a space. Maybe also use ss or showsims for shorthand.");
			Program.newline ();
		}
		
		public static void helpquit ()
		{
			Program.newline ();
			Program.cout ("Quits the application. On program exit, all simulations and active threads are currently reset and do not persist on relaunch. Not case senstive. May also use exit, quit, close, q or q! for shorthand.");
			Program.newline ();
		}
		#endregion
		
		#region Processes
		public static void stop ()
		{
			if (Program.Simulations.Count > Program.ChosenSimIndex) {
				Program.newline ();
				Program.Simulations [Program.ChosenSimIndex].Stop ();
				Program.cout ("Stop command sent to simulation.");
				Program.newline ();
				
			} else {
				Program.newline ();
				Program.cout ("Simulation has not been chosen.");	
				Program.newline ();
			}
		}
		
		public static void start ()
		{
			if (Program.Simulations.Count > Program.ChosenSimIndex) {
				Program.newline ();
				Program.Simulations [Program.ChosenSimIndex].Start ();
				Program.cout ("Start command sent to simulation.");
				Program.newline ();
				
			} else {
				Program.newline ();
				Program.cout ("Simulation has not been chosen.");	
				Program.newline ();
			}
		}
		
		//TODO: consider expanding to include parameters
		public static void call (string methodname)
		{
			if (Program.Simulations.Count > Program.ChosenSimIndex) {
				Program.newline ();
				Program.cout ("Functions: ");
				Program.newline ();
				MethodInfo mi = typeof(Core.Simulation).GetMethod (methodname, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly);
				if (mi != null) {
					mi.Invoke (Program.Simulations [Program.ChosenSimIndex], null);
					Program.cout ("Successfully invoked (" + methodname + ").");
					Program.newline ();
				} else {
					Program.newline ();
					Program.cout ("Method (" + methodname + ") does not exist.");	
					Program.newline ();
				}
				
			} else {
				Program.newline ();
				Program.cout ("Simulation has not been chosen.");	
				Program.newline ();
			}
		}
		
		public static void showfunctions ()
		{
			if (Program.Simulations.Count > Program.ChosenSimIndex) {
				Program.newline ();
				Program.cout ("Functions: ");
				Program.newline ();
				//TODO: currently also returns properties
				MethodInfo[] ms = typeof(Core.Simulation).GetMethods (BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly);
				ms.ToList ().ForEach (x => {
					Program.cout (x.Name);
				});
				Program.newline ();
			} else {
				Program.newline ();
				Program.cout ("Simulation has not been chosen.");	
				Program.newline ();
			}
		}
		
		public static void removesimulation (int index)
		{
			index = System.Math.Abs (index);
			if (Program.Simulations.Count > index) {
				Program.newline ();
				Program.Simulations [index].Stop ();
				Program.Simulations [index] = null;
				Program.Simulations.RemoveAt (index);
				Program.ChosenSimIndex = Program.Simulations.Count + 1;
				Program.cout ("Simulation (" + index.ToString () + ") removed. All simulations have been reindexed and chosen sim has been reset. Use show simulations to see new indexes.");		
				Program.newline ();
			} else {
				Program.newline ();
				Program.cout ("Simulation does not exist at specified index.");	
				Program.newline ();
			}
		}
		
		public static void updatevariable (string variablename, int variablevalue)
		{
			if (Program.Simulations.Count > Program.ChosenSimIndex) {
				Program.newline ();
				PropertyInfo pi = typeof(Core.Simulation).GetProperty (variablename, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
				if (pi != null) {
					pi.SetValue (
				Program.Simulations [Program.ChosenSimIndex], variablevalue, null);
					Program.cout ("Variable (" + variablename + ") set to (" + variablevalue.ToString () + ") for simulation (" + Program.ChosenSimIndex.ToString ()
			              + ").");
					Program.newline ();
				} else {
					Program.newline ();
					Program.cout ("Variable (" + variablename + ") does not exist.");		
					Program.newline ();
				}
			} else {
				Program.newline ();
				Program.cout ("Simulation has not been chosen.");	
				Program.newline ();
			}
		}
		
		public static void showvariables ()
		{
			if (Program.Simulations.Count > Program.ChosenSimIndex) {
				Program.newline ();
				Program.cout ("Accessible variables in simulation (" + Program.ChosenSimIndex.ToString () + ") :");
				Core.Simulation x = Program.Simulations [Program.ChosenSimIndex];
				Program.newline ();
				//TODO: consider replace with reflection get properties
				Program.cout ("MaxYears = " + x.MaxYears);
				Program.cout ("NumberOfPersons = " + x.NumberOfPersons);
				Program.cout ("TotalPersonPurposes = " + x.TotalPersonPurposes);
				Program.cout ("MaxPossiblePurposes = " + x.MaxPossiblePurposes);
				Program.cout ("TimeDilation = " + x.TimeDilation);
				Program.newline ();
			} else {
				Program.newline ();
				Program.cout ("Simulation has not been chosen.");	
				Program.newline ();
			}
		}
		
		public static void choosesimulation (int index)
		{
			Program.newline ();
			index = System.Math.Abs (index);
			if (Program.Simulations.Count > index) {
				Program.ChosenSimIndex = index;
				Program.cout ("Simulation (" + index.ToString () + ") chosen.");
				Program.newline ();
				showvariables ();
			} else {
				Program.newline ();
				Program.cout ("Simulation does not exist at specified index.");	
				Program.newline ();
			}
		}
		
		public static void showsimulations ()
		{
			if (Program.Simulations.Count > 0) {
				Program.newline ();
				Program.cout ("Simulations:");
				Program.newline ();
				int simcount = 0;
				Program.Simulations.ToList ().ForEach (x => {
					Program.cout ("Simulation Index: " + simcount.ToString ());	
					simcount += 1;
					Program.cout ("Simulation Status: " + ((x.Running == true) ? "ACTIVE RUNNING" : "IDLE STOPPED"));	
					Program.cout ("Simulated Year: " + x.Time.Year + "y " + 
				             x.Time.Month + "m " + x.Time.Day + "d " + x.Time.Hour + "h " + x.Time.Minute + "m " + x.Time.Second + "s");
					Program.cout ("Year of E.L.E: " + x.MaxYears);
					Program.cout ("Current Population: " + x.NumberOfPersons);
					Program.cout ("Purposes per Person: " + x.TotalPersonPurposes);
					Program.cout ("Total Available Purposes: " + x.MaxPossiblePurposes);
					x.SyncIndexes ();
					Program.cout ("Total Active Purposes: " + x.GetTotalActivePurposes ().ToString ());
					Program.cout ("Total Recurring Purposes: " + x.GetTotalRecurringPurposes ().ToString ());
					Program.cout ("Total Distinct Purposes: " + x.GetTotalDistinctPurposes ().ToString ());
					List<Core.MatchingPurpose> mps = x.GetMatchingPurposeIds ();
					string sp = "0";
					string hp = "unknown";
					if (mps.Count () > 0) {
						sp = mps.Count ().ToString ();
						if (mps.Count () >= 1) {
							//already returned ordered by highest. top 1 should be distinct count, if not then hp unknown.
							if (mps.Where (m => m.Count == mps.Take (1).Single ().Count).Count () == 1) {
								hp = mps.Take (1).Single ().Index.ToString ();
							}
						}
					}
					Program.cout ("Total Shared Purposes: " + sp);
					Program.newline ();
					foreach (Core.MatchingPurpose mp in mps.Take(10)) {
						Program.cout ("Index: " + mp.Index.ToString () + "\t" + " Occurrences: " + mp.Count.ToString ()); 
					}
					if (mps.Count () > 10) {
						Program.newline ();
						Program.cout ("More than 10 shared purposes. Only top 10 are shown to avoid screen overflow.");
					}
					Program.newline ();
					Program.cout ("Higher Purpose Index:  " + hp);
					Program.newline ();
				});
			} else {
				Program.newline ();
				Program.cout ("No simulations created or loaded.");
				Program.newline ();
			}
		}
		
		public static void newsimulation ()
		{
			Program.newline ();
			Core.Simulation s = new Core.Simulation ();
			;
			Program.Simulations.Add (s);
			Program.ChosenSimIndex = Program.Simulations.Count () + 1;
			Program.cout ("New simulation created and added to list. Items have been reindexed and chosen sim has been reset.");
			Program.newline ();
		}
		#endregion
	}
}

